﻿#region Header
// ------------------------ Licence / Copyright ------------------------
// 
// ScrumTable for Scrum Meeting Support
// Copyright © HSR - Hochschule für Technik Rapperswil
// All Rights Reserved
// 
// Author:
//  Michael Gfeller Silvan Gehrig Patrick Boos
// 
// ---------------------------------------------------------------------
#endregion

#region Usings

using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Serialization;
using ScrumTable.BL.DM.DataManagement;
using ScrumTable.Common.Collections;
using ScrumTable.Common.FileSystem;

#endregion

namespace ScrumTable.Config.ElementManagement
{
 
    /// <summary>
    /// Represents a ConfigManager
    /// </summary>
    [XmlRoot(ElementName = "ConfigRoot")]
    public class ElementManager : ISerializationMarker<ElementManager>
    {
        #region Declarations
        //--------------------------------------------------------------------
        // Declarations
        //--------------------------------------------------------------------

        private readonly List<ElementConfig> _configList = new List<ElementConfig>();
        private ElementConfig _currentConfig;
        private readonly Dictionary<string, string> _defaultStateImages = new Dictionary<string, string>() { { "Active", "StateActive" }, { "InProgress", "StateInProgress" }, { "ToVerify", "StateVerify" }, { "Resolved", "StateVerify" }, { "Closed", "StateClosed" } };
        private List<ScrumState> _closedState;

        #endregion

        #region Properties
        //--------------------------------------------------------------------
        // Properties
        //--------------------------------------------------------------------


        /// <summary>
        /// Gets pr sets the cultor info (screen language)
        /// </summary>
        [XmlAttribute]
        public string CultureInfo { get; set; }

        /// <summary>
        /// Gets or sets the config list.
        /// </summary>
        [XmlArray("ConfigList")]
        [XmlArrayItem(Type = typeof(ElementConfig), ElementName = "Config")]
        public List<ElementConfig> ConfigList
        {
            get { return _configList; }
        }

        /// <summary>
        /// Gets the deserialized current configuration settings instance.
        /// </summary>
        [XmlIgnore]
        public ElementConfig ElementConfig
        {
            get
            {
                
                if (_configList.Count == 0 && _currentConfig == null )
                {
                    throw new NullReferenceException("currentConfig");
                }
                return _currentConfig;
            }
            set
            {
                _currentConfig = value; 
            }
        }

        /// <summary>
        /// Contains the serializer
        /// </summary>
        [XmlIgnore]
        public ConfigSerializer<ElementManager> Serializer { get; set; }
 
        /// <summary>
        /// Checks if the state is closed
        /// </summary>
        /// <param name="state"></param>
        /// <returns></returns>
        public bool IsClosedState(ScrumState state)
        {
            return _closedState.Contains(state);
        }

        /// <summary>
        /// Contains default configs (based on template names!)
        /// </summary>
        [XmlIgnore] public static readonly ElementManager DefaultElementManager;
        #endregion

        #region Constructors / Destructor

        //--------------------------------------------------------------------
        // Constructors / Destructor
        //--------------------------------------------------------------------

        /// <summary>
        /// DO NOT USE THIS CONSTRUCTOR FROM GLOBAL CODE. THE SERIALIZER NEEDS
        /// A PUBLIC CONSTRUCTOR!
        /// </summary>
        public ElementManager()
        {
            CultureInfo = "en-US";
        }


        static ElementManager()
        {
            DefaultElementManager = ElementManagerFactory.CreateConfigManager(DataFolderUtil.GetConfigFile("DefaultElementConfig.xml"), DataFolderUtil.GetConfigUserPath("DefaultElementConfig.xml"));
        }
        #endregion

        #region Methods
        //--------------------------------------------------------------------
        // Methods
        //--------------------------------------------------------------------
        
      
        /// <summary>
        /// Sets the current Config
        /// </summary>
        /// <param name="project">project id</param>
        public void SetCurrentConfig(Project project)
        {
            var tmpConfig = _configList.Find(x => x.ProjectId == project.Name && x.SchemaName == project.Context.DriverInfo.Name);
            if(tmpConfig == null)
            {

                tmpConfig = DefaultElementManager.ConfigList.Find(x => x.SchemaName == project.Context.DriverInfo.Name);
                if (tmpConfig == null)
                {
                    tmpConfig = new ElementConfig()
                                    {
                                        ProjectId = project.Name,
                                        SchemaName = project.Context.DriverInfo.Name,
                                        StateConfigBug = ConvertScrumStateToPair(project.Types.BugType.StateMappings).ToList(),
                                        StateConfigStory = ConvertScrumStateToPair(project.Types.UserStoryType.StateMappings).ToList(),
                                        StateConfigTask = ConvertScrumStateToPair(project.Types.TaskType.StateMappings).ToList()
                                    };

                    SortStates(tmpConfig);
                    _configList.Add(tmpConfig);
                }
                else
                {
                    tmpConfig = tmpConfig.Clone();
                    tmpConfig.ConfigName = project.Name;
                    tmpConfig.ProjectId = project.Name;
                    _configList.Add(tmpConfig);
                }
            }
            if (ElementConfig == null || tmpConfig != ElementConfig)
            {
                ElementConfig = _configList.Find(x => x.ProjectId == project.Name && x.SchemaName == project.Context.DriverInfo.Name);
            }

            
            ValidateConfig(project.Types.BugType, ElementConfig.StateConfigBug);
            ValidateConfig(project.Types.TaskType, ElementConfig.StateConfigTask);
            ValidateConfig(project.Types.UserStoryType, ElementConfig.StateConfigStory);

            ConfigureStateOnProject(project.Types.BugType, ElementConfig.StateConfigBug);
            ConfigureStateOnProject(project.Types.TaskType, ElementConfig.StateConfigTask);
            ConfigureStateOnProject(project.Types.UserStoryType, ElementConfig.StateConfigStory);
          
            _closedState= new List<ScrumState>();
            foreach(var state in ElementConfig.ClosedStateInternal)
            {
                _closedState.Add(new ScrumState(state.Right,state.Left));
            } 
        }

        ///<summary>
        /// Sets the properties to a reports
        ///</summary>
        ///<param name="name"></param>
        ///<param name="pairs"></param>
        public void SetReport(string name, List<Pair<string,string>> pairs)
        {
            ReportConfig config = GetReport(name);
            if(config==null)
            {
               config = new ReportConfig(name);
               ElementConfig.Reports.Add(config);
            }
            config.Parameter = (from pair in pairs select new SerializablePair<string,string>(pair.Left,pair.Right)).ToList();
        }

        /// <summary>
        /// gets the config to a report
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public ReportConfig GetReport(string name)
        {
            return ElementConfig.Reports.FirstOrDefault(x => x.Name == name);
        }

        /// <summary>
        /// Gets the config for a specific control
        /// </summary>
        /// <param name="controlName"></param>
        /// <returns></returns>
        public ReportConfig GetReportControl(string controlName)
        {
            var reportControl =  ElementConfig.ReportControl.FirstOrDefault((x) => x.Left == controlName);
            if(reportControl != null)
            {
                return GetReport(reportControl.Right);
            }
            return null;
        }

        /// <summary>
        /// Set the report for a control
        /// </summary>
        /// <param name="controlName"></param>
        /// <param name="reportConfig"></param>
        /// <returns></returns>
        public void SetReportControl(string controlName, ReportConfig reportConfig)
        {
            var reportControl = ElementConfig.ReportControl.FirstOrDefault((x) => x.Left == controlName);
            if (reportConfig == null && reportControl != null)
            {
                ElementConfig.ReportControl.Remove(reportControl);
            }
            else if( reportConfig == null)
            {
                //nothing   
            }
            else if (reportControl == null)
            {
                ElementConfig.ReportControl.Add(new SerializablePair<string, string>(controlName, reportConfig.Name));
            }
            else
            {
                reportControl.Right = reportConfig.Name;
            }
        }

        private void SortStates(ElementConfig config)
        {
            var stateOrder = GetStateOrder(config);

            //set all states to the same index
            SetIndex(stateOrder, config.StateConfigBug);
            SetIndex(stateOrder, config.StateConfigStory);
            SetIndex(stateOrder, config.StateConfigTask);
        }

        private List<string> GetStateOrder(ElementConfig config)
        {
            //get all different states
            List<string> stateOrder = config.StateConfigStory.Select(state => state.Right).ToList();
            AddToOrderIndex(stateOrder, config.StateConfigBug);
            AddToOrderIndex(stateOrder, config.StateConfigTask);
            return stateOrder;
        }

        private void AddToOrderIndex(List<string> stateOrder, List<SerializablePair<int, string>> stateConfigTask)
        {
            foreach (var state in stateConfigTask)
            {
                if (!stateOrder.Contains(state.Right))
                {
                    stateOrder.Insert(stateOrder.Count - 2, state.Right);
                }
            }
        }

        private void SetIndex(List<string> stateOrder, List<SerializablePair<int, string>> list)
        {
            foreach (var state in list)
            {
                state.Left = stateOrder.IndexOf(state.Right);
            }
        }

        private void ValidateConfig(ScrumItemType type, List<SerializablePair<int, string>> config)
        {
            //check if one element has been deleted -> restore
            foreach( var state in type.StateMappings)
            {
                var stateconfig = config.FirstOrDefault(configState => configState.Right == state.Name);
                if(stateconfig == null)
                {
                    var indexInList = GetStateOrder(ElementConfig).IndexOf(state.Name); //if the equal state is already in the list. Take the same index
                    
                    int idValue = config.IsEmpty() ? 0 :
                        indexInList >= 0 ? indexInList : config.Max(item => item.Left) + 100;

                    config.Add(new SerializablePair<int, string>(idValue, state.Name));
                }
            }

            //check if one element has been deleted on server and is still in config file-> delete
            for (int index = 0; index < config.Count; index++)
            {
                var state = config[index];
                var statetfs = type.StateMappings.FirstOrDefault(tfsstate => tfsstate.Name == state.Right);
                if (statetfs == null)
                {
                    config.Remove(state);
                }
            }

            //check if one item has the same id as an other one in the same group -> change!
            foreach (var state in config)
            {
                SerializablePair<int, string> lastItem;
                while ((lastItem = config.FindLast(item => state.Left == item.Left)) != state)
                {
                   lastItem.Left = config.Max(item => item.Left) + 100;
                }

                SerializablePair<int, string> firstItem;
                while ((firstItem = config.Find(item => state.Left == item.Left)) != state)
                {
                    firstItem.Left = config.Max(item => item.Left) + 100;
                }
            }
        }

        private void ConfigureStateOnProject(ScrumItemType type, List<SerializablePair<int, string>> config)
        {
            foreach(var state in config)
            {
                type.StateMappings[state.Right].Id = state.Left;
            }
        }

        private IEnumerable<SerializablePair<int, string>> ConvertScrumStateToPair(IEnumerable<ScrumState> states)
        {
            return from state in states select new SerializablePair<int, string>(state.Id, state.Name);

        }
        
        /// <summary>
        /// Gets the config for a target
        /// </summary>
        /// <param name="target"></param>
        /// <returns></returns>
        public TargetConfig GetTargetConfig(string target)
        {
            foreach (var config in ElementConfig.TargetConfigs)
            {
                if (config.TargetName == target)
                {
                    return config;
                }
            }
            var targetConfig = TargetConfig.GetDefaultConfig(target);
            if(targetConfig !=null)
            {
                ElementConfig.TargetConfigs.Add(targetConfig);
            }
            return targetConfig;
        }

        /// <summary>
        /// Gets a ressource string for an image (or null if no image is avaible) 
        /// </summary>
        /// <returns></returns>
        public string GetImgRessourceString(ScrumState state)
        {
            var pair = ElementConfig.StateImages.FirstOrDefault(x => x.Left == state.Id);
            if( pair == null)
            {
                pair = new SerializablePair<int, string>(state.Id, _defaultStateImages.TryGetValue(state.Name));

                ElementConfig.StateImages.Add(pair);
            }
            return pair.Right;
        }

        /// <summary>
        /// Saves the config to a file.
        /// </summary>
        public void Save()
        {
            Serializer.Serialize(this);
        }

        /// <summary>
        /// Saves the config to a file.
        /// </summary>
        /// <param name="path">path to store the file.</param>
        public void Save(string path)
        {
            new ConfigSerializer<ElementManager>(path, path).Serialize(this);
        }

        /// <summary>
        /// Calculates the numbers of states from the config
        /// </summary>
        /// <param name="config"></param>
        /// <returns></returns>
        public int CalcMaxStatesForTarget(TargetConfig config)
        {
            return GetStates(config).Count();
        }

        /// <summary>
        /// Gets all states for this config
        /// </summary>
        /// <param name="config"></param>
        /// <returns></returns>
        public IEnumerable<ScrumState> GetStates(TargetConfig config)
        {
            List<SerializablePair<int, string>> list = new List<SerializablePair<int, string>>();
        
            if (config.IsTask)
            {
                list.AddRange(ElementConfig.StateConfigTask);
            }
            if (config.IsStory)
            {
                list.AddRange(ElementConfig.StateConfigStory);
            }
            if (config.IsBug)
            {
                list.AddRange(ElementConfig.StateConfigBug);
            }
            foreach( var exclude in config.ExcludedStates)
            {
                list.RemoveAll(x=> exclude==x.Left);
            }
            return (from item in list.OrderBy(x => x.Left) select new ScrumState(item.Right, item.Left)).Distinct();
        }

        #endregion

        #region Events
        //--------------------------------------------------------------------
        // Events
        //--------------------------------------------------------------------

        #endregion
    }
}
